use std::{collections::HashMap, fs::File, io::Read, path::Path};

use serde::Deserialize;

use common_base::{once_cell::sync::OnceCell, prelude::*, toml};

use crate::data::ParamObject;

static EAI_SERVER_MAP: OnceCell<HashMap<String, EaiServer>> = OnceCell::new();
static LOCAL_EAI_SERVER_ID: OnceCell<String> = OnceCell::new();

#[derive(Deserialize, Debug)]
pub struct EaiServer {
    pub(crate) id: String,
    pub(crate) caption: String,
    pub(crate) server_protocol: String,
    pub(crate) server_host: String,
    pub(crate) server_port: usize,
    pub(crate) db_name: String,
    pub(crate) db_no: String,
    // TODO:
    // pub(crate) server_product: String,
}

impl EaiServer {
    pub fn initialize(
        eai_server_file: &str,
        local_eai_server_id_: &str,
    ) -> Result<&'static EaiServer> {
        let mut file = File::open(Path::new(eai_server_file)).map_err(|e| ErrorCode::from(e))?;
        let mut eai_server_content = String::new();
        file.read_to_string(&mut eai_server_content)
            .map_err(|e| ErrorCode::from(e))?;
        let mut eai_servers_in_map: HashMap<String, Vec<EaiServer>> =
            toml::from_str(&eai_server_content).map_err(|e| ErrorCode::from(e))?;
        let eai_servers = eai_servers_in_map
            .remove("eai_server")
            .expect("eai_server file format invalid");

        let mut eai_server_map = HashMap::with_capacity(eai_servers.len());
        eai_servers.into_iter().for_each(|eai_server| {
            eai_server_map.insert(eai_server.id.to_string(), eai_server);
        });
        EAI_SERVER_MAP
            .set(eai_server_map)
            .expect("EAI_SERVER_MAP can not be initialize twice");

        let local_eai_server = EAI_SERVER_MAP
            .get()
            .unwrap()
            .get(local_eai_server_id_)
            .expect("can not find loal_eai_server in eai_server file");

        LOCAL_EAI_SERVER_ID
            .set(local_eai_server_id_.to_owned())
            .expect("LOCAL_EAI_SERVER_ID can not be initialize twice");

        Ok(local_eai_server)
    }

    pub fn get_local_eai_server_id() -> &'static str {
        LOCAL_EAI_SERVER_ID
            .get()
            .expect("local_eai_server_id have not initialize")
            .as_str()
    }

    pub fn get_eai_server(server_id: &str) -> Option<&Self> {
        // TODO: be suere this is correct, use `dbname_dbno` as prefix?
        let db_name = ParamObject::get_global_value_or_default("DataBaseName", "");
        let db_no = ParamObject::get_global_value_or_default("DBNO", "");
        let server_key = match (db_name.is_empty(), db_no.is_empty()) {
            (true, true) => server_id.to_owned(),
            (true, false) => format!("{}_{}", db_no, server_id),
            (false, true) => format!("{}_{}", db_name, server_id),
            (false, false) => format!("{}_{}_{}", db_name, db_no, server_id),
        };

        let eai_server = EAI_SERVER_MAP
            .get()
            .expect("eai_server_map must be init")
            .get(server_key.as_str());
        if eai_server.is_some() {
            eai_server
        } else {
            EAI_SERVER_MAP
                .get()
                .expect("eai_server_map must be init")
                .get(server_id)
        }
    }

    pub fn get_eai_server_uri(&self) -> String {
        format!(
            "{}://{}:{}",
            self.server_protocol, self.server_host, self.server_port,
        )
    }

    pub fn get_eai_server_listen_uri(&self) -> String {
        format!("{}:{}", self.server_host, self.server_port,)
    }
}
